/*
Kakine
ActionScript Framework
Copyright © 2011, PUNY, LLC.

This file is part of KAKINE.

KAKINE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

KAKINE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with KAKINE.  If not, see <http://www.gnu.org/licenses/>.
*/ 
package com.puny.ball.controllers
{
	import com.puny.ball.utils.Vector2D;
	import com.puny.ball.utils.events.WorldEvent;
	import com.puny.ball.utils.handlers.DefaultWorldScrollHandler;
	import com.puny.ball.utils.handlers.IWorldScrollHandler;
	import com.puny.ball.views.BallView;
	import com.puny.ball.views.maps.Accelerator;
	import com.puny.ball.views.maps.Friction;
	import com.puny.ball.views.maps.MapView;
	import com.puny.ball.views.maps.Trap;
	import com.puny.ball.views.obstacles.MovingBoxObstacle;
	import com.puny.ball.views.obstacles.MovingCircleObstacle;
	import com.puny.ball.views.obstacles.Obstacle;
	import com.puny.mvc.controllers.Controller;
	import com.puny.mvc.helpers.FrameHelper;
	import com.puny.mvc.models.Model;
	
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;

	public class WorldController extends Controller
	{
		private static const BALL:Vector2D = new Vector2D();
		private static var _debugFlag:Boolean = true;

		private const BALLSTOP:Event = new WorldEvent(WorldEvent.BALL_STOP);
		private const WALL:WorldEvent = new WorldEvent(WorldEvent.HIT_WALL);

		private const _p:Vector2D = new Vector2D();
		private const _a:Vector2D = new Vector2D();
		private const _v:Vector2D = new Vector2D();
		private const _nphp:Vector2D = new Vector2D();
		private const _org:Point = new Point();
		private const _np:Vector2D = new Vector2D();
		private const _op:Vector2D = new Vector2D();

		private var _ballView:BallView;
		private var _map:MapView;
		private var _ball:Model;

		private var _moving:int;
		private var _impactDebug:String;
		private var _counter:uint = 24;
		private var _holeScroll:IWorldScrollHandler;
		private var _openloop:Boolean = true;
		private var _id:int;
		private var _friction:Number = 0.2;

		/**
		 * @param src source object which includes a map asset
		 * @param name exported name of a map asset
		 * @example Assumed "MyWorld" movieclip is exported in "assets/MyAssets.swf".
		 * <listing version="3.0">
		 * var myWorld:WorldController = new WorldController("assets/MyAssets.swf", "MyWorld");
		 * </listing>
		 * 
		 * @example To use a customized map and a customized ball
		 * <listing version="3.0">
		 * public class MyWorldController extends WorldController {
		 *	public function MyWorldController () {
		 *		mapView = new MyWorldView();
		 * 		ballView = new MyBallView();
		 *		super();
		 *	}
		 * }
		 * </listing>
		 */
		public function WorldController(src:Object=null,name:String=null) {
			if (_map == null) {
				_map = new MapView(src,name);
			}
			if (_ballView == null) {
				_ballView = new BallView();
			}
			super();
		}
		
		/**
		 * 
		 * @param value
		 * 
		 */
		public static function set debugFlag(value:Boolean):void {
			_debugFlag = value;
		}
				
		/**
		 * 
		 * @return 
		 * 
		 */
		public function get mapView():MapView {
			return _map;
		}
		
		/**
		 * 
		 * @param value
		 * 
		 */
		public function set mapView(value:MapView):void {
			_map = value;
		}
		
		/**
		 * 
		 * @param value
		 * 
		 */
		protected function set ballView(value:BallView):void {
			_ballView = value;
		}
		
		/**
		 * 
		 * @return 
		 * 
		 */
		protected function get ballView():BallView {
			return _ballView;
		}
		
		/**
		 * 
		 * @return velocity of a ball
		 * 
		 */
		protected function get velocity():Vector2D {
			return _v;
		}
		/**
		 * <code>p</code>(<code>Vector2D</code> instance), <code>vx</code>, <code>vy</code>,
		 * <code>r</code>, and <code>p0</code>(<code>Vector2D</code> instance) would be available.
		 * @return a ball model
		 * 
		 */
		public function get ball():Model {
			return _ball;
		}
		
		/**
		 * Kinetic friction: 0.2 in default 
		 * @param value
		 * 
		 */
		public function set friction(value:Number):void {
			_friction = value;
		}
		
		/**
		 * place a ball at (<code>px</code>, <code>px</code>) 
		 * @param px
		 * @param py
		 * 
		 * At least the ball can be moved until <code>counter</code>(=24 in default) frames pass.
		 */
		public function placeBall(px:Number,py:Number):void {
			_ball.p.x = px;
			_ball.p.y = py;
			_ball.vx = 0;
			_ball.vy = 0;
			restartMainFrame();
			_ballView.visible = true;
		}

		/**
		 * 
		 * 
		 */
		public function scroll():void {
			_holeScroll = mapView.scrollHandler.shift();
			if (_holeScroll != null) {
				_holeScroll.updater = updateBallPosition;
				FrameHelper.setupWithHandler(this, _holeScroll);
			}
		}
		
		/**
		 * Counter to make sure a ball stops.
		 * Wait <code>counter</code> frames to see if a ball stops.
		 * @param value
		 * 
		 */
		public function set counter(value:uint):void {
			_counter = value;
		}

		/**
		 * Let a ball move along the given direction.
		 * @param pt direction of impact
		 * 
		 */
		public function impact(pt:Point):void {
			_ball.vx = pt.x;
			_ball.vy = pt.y;
			if (!restartMainFrame()) {
				initialize();
			}
		}

		/**
		 * Rotate the given id's obstacle with the given angle. 
		 * @param id an order of an obstacle on map
		 * @param value angle (degree)
		 * 
		 */
		public function rotate(id:int,value:Number):void {
			_id = id;
			trace("obstacles[", _map.obstacles[id], "].rotation=", value);
			try {
				_map.obstacles[id].rotation =value;
				_map.obstacles[id].rotation =value;
			} catch(e:Error) {
				trace(e);
			}
		}
		
		public function jump(dir:String, pos:int):void {
			var mc:MovieClip = _map.obstacles[_id];
			if (mc is MovingBoxObstacle) {
				(mc as MovingBoxObstacle).resume(dir, pos);
			} else if (mc is MovingCircleObstacle) {
				(mc as MovingCircleObstacle).resume(dir, pos);
			}
		}
				
		override protected function setup():void {
			_ball = new Model();
//			_ballView = new BallView();
			_ballView.visible = false;
			addChild(_map);
			addChild(_ballView);

			_ball.p = BALL;
			_ball.vx = 0;
			_ball.vy = 0;
		}
				
		override public function beforeReady():void {
			stage.addEventListener(Event.RESIZE, resizedHandler);
			var mc:MovieClip;
			for each(mc in _map.obstacles) {
				mc.debug = _debugFlag;
				mc.inflate(_ballView.radius);
			}
			for each(mc in mapView.topLayers) {
				addChild(mc);
				mc.org = new Point(mc.x,mc.y);
			}

			_holeScroll = mapView.scrollHandler.shift();
			if (_holeScroll == null) {
				_holeScroll = new DefaultWorldScrollHandler();
			}
			_holeScroll.updater = updateBallPosition;
		}

		override public function beforeOpen():void {
			super.beforeOpen();
			_holeScroll.before();
			addEventListener(MouseEvent.CLICK, skipHandler);	
		}
		
		private function skipHandler(event:Event):void {
			removeEventListener(MouseEvent.CLICK, skipHandler);
			_openloop = false;
		}
		
		override public function procedureOpen():Boolean {
			return _openloop && _holeScroll.loop();
		}
		
		override public function afterOpen():void {
			super.afterOpen();
			_holeScroll.after();
			_holeScroll = null;
		}
		
		override public function beforeMainFrame():void {
			calcOrigine();
			initialize();
			impactInfo();
			trace(debug());
			//			try {
			//				System.setClipboard(debug());
			//			} catch (e:Error) {
			//				trace(e);
			//			}
		}

		override public function procedureMainFrame():Boolean {
			//Fair way
			decelerate(_friction);
			var px:Number = _ball.p.x+_org.x;
			var py:Number = _ball.p.y+_org.y;
			for each(var f:Friction in _map.frictions) {
				if (f.hitTestPoint(px, py, true)) {
					decelerate(f.power); 
				}
			}
			
			var gx:Number = px+_map.x;
			var gy:Number = py+_map.y;
			for each(var accelerator:Accelerator in _map.accelerators) {
				if (accelerator.hitTestPoint(gx, gy, true)) {
					//					accelerator.setVelocity(_v.x, _v.y);
					_a.y += accelerator.acceleration.y;
					_a.x += accelerator.acceleration.x;
				}
			}
			addForce(_a);
			for each(var trap:Trap in _map.traps) {
				trap.hitTestPoint(gx, gy, true);
			}
			forward();
			if (_v.length < 2) {
				_moving--;
			} else {
				_moving = _counter + (_holeScroll == null ? 0 : _holeScroll.frames);
			}
			
			return _moving > 0;
		}
		
		override public function afterMainFrame():void {
			_holeScroll = null;
			dispatchEvent(BALLSTOP.clone());
		}
		
		override public function beforeClose():void {
			stage.removeEventListener(Event.RESIZE, resizedHandler);
		}
		
		override public function afterClose():void {
			for each(var mc:MovieClip in mapView.topLayers) {
				removeChild(mc);
			}
			super.afterClose();
		}

//		override public function afterOpen():void {
//			calcOrigine();
//		}
		
		private function resizedHandler(event:Event):void {
			calcOrigine();
		}
		
		override public function set x(value:Number):void {
			super.x = value;
			calcOrigine();
		}
		
		override public function set y(value:Number):void {
			super.y = value;
			calcOrigine();
		}
		
		private function calcOrigine():void {
			var pt:Point = localToGlobal(new Point(0,0));
			_org.x = pt.x;
			_org.y = pt.y;
		}		
		
//		protected function drop():void {
//			_ballView.visible = true;
//		}
		
		private function initialize():void {
			_ball.r = _ballView.radius
			_ball.p0 = new Vector2D(_ball.p.x - _ball.vx, _ball.p.y - _ball.vy);

			updateBallPosition();

			_moving = _counter;
		}
		
		private function forward():void {
//			trace(_ball.p,_ball.p0);
			_p.x = _ball.p.x - _ball.p0.x + _a.x;
			_p.y = _ball.p.y - _ball.p0.y + _a.y;
			_ball.p0.x = _ball.p.x;
			_ball.p0.y = _ball.p.y;
//			trace(_ball.p,_ball.p0);

			if (!collision(_ball.p, _p)) {
				_ball.p.x = _ball.p.x + _p.x;
				_ball.p.y = _ball.p.y + _p.y;
			}
			
			updateBallPosition();
		}
		
		private function updateBallPosition():void {
//			trace("Old:",_ball.p0.x,_ball.p0.y);
			_ballView.x = _ball.p.x + _map.x;
			_ballView.y = _ball.p.y + _map.y;
			for each(var mc:MovieClip in _map.topLayers) {
//				trace(mc.org,_map.x,_map.y);
				mc.x = mc.org.x + _map.x;
				mc.y = mc.org.y + _map.y;
			}
//			trace("New:",_ball.p.x,_ball.p.y);
		}
		
		private function collision(p0:Vector2D, p:Vector2D,lastObj:Obstacle = null):Boolean {
			var t:Number;
			var tShort:Number = 2;
			var hitObj:Obstacle;
			var event:WorldEvent;
			for each(var ob:Obstacle in _map.obstacles) {
				if (ob != lastObj) {
					ob.pos = tShort;
					if (ob.collision(p0,p)) {
						if (ob.pos < tShort) {
							_np.x = ob.newPos.x;
							_np.y = ob.newPos.y;
							_op.x = ob.mirroredPos.x;
							_op.y = ob.mirroredPos.y;
//							trace("np=", _np);
//							trace("op=", _op);
							tShort = ob.pos;
							hitObj = ob;
						}
					}
				}
			}
			if (hitObj != null) {
				_ball.p.x = _np.x;
				_ball.p.y = _np.y;
				_ball.p0.x = _op.x;
				_ball.p0.y = _op.y;
				_nphp.x = _np.x - hitObj.hitPos.x;
				_nphp.y = _np.y - hitObj.hitPos.y;
//				event = WALL.clone();
				WALL.obstacle = hitObj;
				dispatchEvent(WALL);
				collision(hitObj.hitPos, _nphp,hitObj);
				return true;
			}
			return false;
		}
		
		protected function addForce(a:Point):void {
		}
				
		private function decelerate(k:Number):void {
			var p:Number;
			var kpl:Number;
			_v.x = _ball.p.x - _ball.p0.x;
			_v.y = _ball.p.y - _ball.p0.y;
			if (_v.length > 0) {
				kpl = k / _v.length;
				_a.x = - kpl * _v.x;
				_a.y = - kpl * _v.y;
				p = _ball.p.x - _ball.p0.x + _a.x;
				if (p*_v.x < 0) {
					_a.x = -(_ball.p.x - _ball.p0.x);
					_a.y = -(_ball.p.y - _ball.p0.y);
				}
				p = _ball.p.y - _ball.p0.y + _a.y;
				if (p*_v.y < 0) {
					_a.x = -(_ball.p.x - _ball.p0.x);
					_a.y = -(_ball.p.y - _ball.p0.y);
				}
			} else {
				_a.x = 0;
				_a.y = 0;
			}
		}
		
		private function impactInfo():void {
			_impactDebug = "\t\t\tctrl.ball.vx =";
			_impactDebug += _ball.vx;
			_impactDebug += ";\r\n";
			_impactDebug += "\t\t\tctrl.ball.vy =";
			_impactDebug += _ball.vy;
			_impactDebug += ";\r\n";
			_impactDebug += "\t\t\tctrl.ball.p = new Vector2D(";
			_impactDebug += _ball.p.x;
			_impactDebug += ",";
			_impactDebug += _ball.p.y;
			_impactDebug += ");\r\n";
		}
		
		
		public function debug():String {
			var string:String = _impactDebug;
			string += "\r\n";
			string += _map.debug();
			return string;
		}
		
	}
}